import IHelper from "@/services/helper.service";
import { IWonewSer } from "@/typing";
import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import axios from "axios";
import { In } from "typeorm"
import { defer, map, concatMap, of, Observable, forkJoin, from, tap, catchError, concat, reduce } from "rxjs";
import * as path from "path"
import * as fs from "fs-extra"
import * as moment from "moment"
import IConstants from "@/core/constants";
import IResponse from "@/services/response.service";
import { DbService } from "@/db/db.service";
import { BiliVideoEntity } from "@/db/entitys/bili-video.entity";
import * as shelljs from "shelljs"
import ILogger from "@/services/logger.service";
import IFfmpeg from "@/services/ffmpeg.service";

@Injectable()
export class BiliOtherService {
  constructor(
    private readonly dbService: DbService
  ) {}
  signParams(t: any, config: IWonewSer.BiliWBI) {
    var i = config.imgKey, a = config.subKey, n, r
    var I = IHelper.md5
    if (i && a) {
      for (var c = (n = i + a,
      r = [],
      [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52].forEach((function(t) {
          n.charAt(t) && r.push(n.charAt(t))
      }
      )),
      r.join("").slice(0, 32)), u = Math.round(Date.now() / 1e3), s = Object.assign({}, t, {
          wts: u
      }), l = Object.keys(s).sort(), f = [], p = /[!'()*]/g, d = 0; d < l.length; d++) {
          var h = l[d]
            , v = s[h];
          v && "string" == typeof v && (v = v.replace(p, "")),
          null != v && f.push("".concat(encodeURIComponent(h), "=").concat(encodeURIComponent(v)))
      }
      var y = f.join("&");
      return {
        ...t,
        w_rid: I(y + c),
        wts: u.toString()
      }
  }
  }
  getWbi(): Observable<IWonewSer.BiliWBI> {
    return defer(() => 
      axios.get(`https://api.bilibili.com/x/web-interface/nav`)
    ).pipe(
      map(res => {
        if (!res.data?.data?.wbi_img?.img_url || !res.data?.data?.wbi_img?.sub_url) {
          throw new HttpException(`Get wbi failed.`, HttpStatus.BAD_REQUEST)
        }
        return {
          imgKey: res.data?.data?.wbi_img?.img_url.split("/").pop().split(".").shift(),
          subKey: res.data?.data?.wbi_img?.sub_url.split("/").pop().split(".").shift(),
        }
      })
    )
  }
  getVideoList(opt: {mid: string, tid: string, pn?: number, ps?: number, last_date?: number}) {
    const pageSize: number = 50
    return from(
      this.dbService.accountRepository.findOneByOrFail({type: 1, status: 1})
    ).pipe(
      map(ent => IHelper.covert2StandardCookieStr(ent.cookie)),
      concatMap(cookie => this.getOnePage(1, pageSize, {...opt, cookie})),
      concatMap(res => {
        const pageCount = Math.ceil(res.total / pageSize)
        return forkJoin(IHelper.getArray(pageCount).map(idx => this.getOnePage(idx + 1, pageSize, opt)))
      }),
      map(res => {
        return res.reduce((acc, val) => {
          return [...acc, ...val.list]
        }, [])
      }),
      map(list => {
        return list.map(item => {
          return {
            bvid: item.bvid, 
            video_bili_bvid: item.bvid, 
            ori_title: item.title,
            title: ((item) => {
              let [part, title] = item.title.split("#").shift().trim().split(".")
              return `P${part}${title}`
            })(item),
            created: item.created,
            format_created: moment(item.created * 1000).format("YYYY-MM-DD HH:mm:ss"),
          }
        })
      }),
      map(list => {
        let filteredList = [...list]
        if (opt.last_date && moment(opt.last_date).isValid() && moment(opt.last_date).unix()) {
          filteredList = filteredList.filter(e => e.created > moment(opt.last_date).unix())
        }
        const pn = +opt.pn || 1, ps = +opt.ps || 50
        return IResponse.success({
          items: filteredList.slice((pn - 1) * ps, pn * ps),
          total: filteredList.length,
          page: pn,
          pageSize: ps,
        })
      })
    )
  }

  downloadVideos() {
    return defer(() => 
      this.dbService.biliVideoRepository.find()
    ).pipe(
      map(list => list.filter(e => !e.video_path)),
      // map(list => list.slice(0, 30)),
      concatMap(list => {
        const groupSize = 10
        let groupCount = Math.ceil(list.length / groupSize)
        const groups = IHelper.getArray(groupCount).map(idx => {
          return list.slice(idx * groupSize, (idx + 1) * groupSize)
        })
        return this.serialDownload(groups)
      })
    )
  }

  serialDownload(groups: BiliVideoEntity[][]) {
    return defer(() => 
      concat(
        ...groups.map((list, idx) => this.parallelDownload(list, idx))
      ).pipe(
        reduce((ls, acc) => [...ls, acc], []),
      )
    )
  }

  parallelDownload(list: BiliVideoEntity[], groupIdx: number) {
    return defer(() => 
      forkJoin(list.map((item, idx) => this.downloadOneVideo(item, idx, groupIdx)))
    )
  }

  downloadOneVideo(ent: BiliVideoEntity, idx: number, groupIdx: number): Observable<BiliVideoEntity> {
    const videoInfo: any = JSON.parse(ent.json)
    return of(null).pipe(
      tap(() => ILogger.info(`idx=${idx} groupIdx=${groupIdx} Start download ${videoInfo.title} ${videoInfo.bvid} `)),
      concatMap(() => this.bbDownload(ent.bvid)),
      tap(() => ILogger.info(`idx=${idx} groupIdx=${groupIdx} Success download ${videoInfo.title} ${videoInfo.bvid}`)),
      concatMap(videoPath => {
        ent.video_path = videoPath
        return this.dbService.biliVideoRepository.save(ent, {reload: true})
      }),
      catchError(error => of(ent))
    )
  }

  getOnePage(pn: number, ps: number, opt: {mid: string, tid: string, cookie?: string}): Observable<{list: any[], total: number, pn: number, ps: number}> {
    return this.getWbi().pipe(
      concatMap(wbi => {
        let params: any = {
          "mid": opt.mid,
          "ps": ps.toString(),
          "tid": opt.tid,
          "special_type": "",
          "pn": pn.toString(),
          "keyword": "",
          "order": "pubdate",
          "platform": "web",
          "web_location": "1550101",
          "order_avoided": "true",
        }
        params = this.signParams(params, wbi)
        return axios.get('https://api.bilibili.com/x/space/wbi/arc/search', {
          params,
          headers: {
            'authority': 'api.bilibili.com',
            'accept': '*/*',
            'accept-language': 'zh-CN,zh;q=0.9',
            'cache-control': 'no-cache',
            'cookie': "buvid3=2D7F9E2D-B89F-41D5-9686-03B8B4616FD2167621infoc; i-wanna-go-back=-1; LIVE_BUVID=AUTO8516415659004109; buvid4=9CD7940A-55F6-99D4-6CD2-F81274DE45BB49188-022012117-y7FzQHcGAvocTX3y3ZTAeQ%3D%3D; buvid_fp_plain=undefined; CURRENT_BLACKGAP=0; is-2022-channel=1; header_theme_version=CLOSE; hit-new-style-dyn=1; CURRENT_FNVAL=4048; CURRENT_PID=00672e10-cd16-11ed-9f1d-a54d650c8c4a; FEED_LIVE_VERSION=V8; nostalgia_conf=-1; DedeUserID=404186078; DedeUserID__ckMd5=f3b1debd6c153fcf; SESSDATA=e9c488fa%2C1712193871%2C467a8%2Aa2CjDlG2jIMeohteWDlu46bsLBiCNpbNUEJEdOZkO7DdFruoTWxoL1YjiAfmzqjOOV9_oSVmJnc01xS3VIMTFRVTVHNXNLeTl6eVZfTDhwU3Q5aGFQd0g0eXBqN1NianRVZi02VC1YYUxFbE05aERkZ3haUEZyTm0wN0NIdGtCc0FFRGxpSDk0RWVBIIEC; bili_jct=de3c9b2be713519c2780b6605b1695ed; enable_web_push=DISABLE; b_nut=100; _uuid=371107714-9319-7EB5-E64A-5109757CFA84231790infoc; hit-dyn-v2=1; fingerprint=0da12c8c0755baf4000e8043773052e5; rpdid=|(J~J|lJ)kuR0J'u~|JuJlJku; home_feed_column=5; CURRENT_QUALITY=0; PVID=1; sid=66ftdyq6; browser_resolution=1920-853; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDY3NjQxMzYsImlhdCI6MTcwNjUwNDg3NiwicGx0IjotMX0.V_MimRRL8SBqIV8S5K0D7h8yBqNL8HO11Df0jNry3dE; bili_ticket_expires=1706764076; buvid_fp=1e3c1ccb485d1f18e457ee7c8f09b662; bsource=search_google; bp_video_offset_404186078=892336810778165287; b_lsid=F11E101AA_18D5D5B756F",
            'origin': 'https://space.bilibili.com',
            'pragma': 'no-cache',
            'referer': 'https://www.bilibili.com/',
            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"macOS"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-site',
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
          }
        })
      }),
      map(res => {
        if (res.data?.code !== 0) {
          throw new HttpException(`getOnePage failed.`, HttpStatus.BAD_REQUEST)
        }
        return {
          list: res.data.data.list.vlist,
          total: res.data.data.page.count,
          pn: res.data.data.page.pn,
          ps: res.data.data.page.ps,
        }
      })
    )
  }

  getVideoProfile(id: string) {
    // ffplay -f lavfi -i "movie=/Users/eros/Documents/code/wonew/third-vpuber-ns/server/public/uploads/temp/t1DYZhYY6FM4zqCW.mp4,delogo=x=20:y=32:w=380:h=70:show=1"
    const task: IWonewSer.CuterTask = {
      delogo: `x=20:y=32:w=380:h=70`,
      bvid: id, 
      cutConfig: {
        start: 0, end: 3.33
      },
      endVideo: path.join(IConstants.publicDir, "assets/12312313dx.mp4"),
      cutTemp: "",
      removeWatermarkTemp: "",
      mergeEndTemp: ""
    }
    return defer(() => 
      this.dbService.biliVideoRepository.findOneBy({bvid: id})
    ).pipe(
      concatMap(ent => {
        if (!ent?.video_path) {
          throw new HttpException(`Video not exist.`, HttpStatus.BAD_REQUEST)
        }
        task.ent = ent
        return IFfmpeg.getVideoDimension(ent.video_path).pipe(
          map(dimension => {
            task.dimension = dimension
            return task
          })
        )
      }),
      concatMap(() => {
        return IFfmpeg.cutVideo(task.ent.video_path, task.cutConfig.start, task.dimension.duration - task.cutConfig.start - task.cutConfig.end).pipe(
          map(cutTemp => {
            task.cutTemp = cutTemp
            return task
          })
        )
      }),
      concatMap(() => 
        IFfmpeg.blurVideo(task.cutTemp, task.delogo).pipe(
          map(removeWatermarkTemp => {
            task.removeWatermarkTemp = removeWatermarkTemp
            return task
          })
        )
      ),
      concatMap(() => 
        IFfmpeg.mergeEndVideo(task.removeWatermarkTemp, task.endVideo, "#ffffff").pipe(
          map(mergeEndTemp => {
            task.mergeEndTemp = mergeEndTemp
            return task
          })
        )
      )
    )
  }

  bbDownload(bvid: string): Observable<string> {
    // let cmd = `BBDown ${bvid} --config-file /Users/eros/.config/BBdown/BBdown.conf`
    const workDir = path.join(IConstants.publicDir, `uploads/bili_temp`)
    fs.ensureDirSync(workDir)
    const filename = IHelper.hash(32)
    let cmd = `BBDown ${bvid} -F ${filename} -tv --work-dir ${workDir}`
    return new Observable(obs => {
      shelljs.exec(cmd, {silent: true}, function(code: number, output) {
        const error = new HttpException(`Download one video failed.`, HttpStatus.BAD_REQUEST)
        if (code !== 0) {
          console.log(output)
          obs.error(error)
          return
        }
        obs.next(path.join(workDir, `${filename}.mp4`))
        obs.complete()
      })
    })
  }

  getPosts(cookies: string, opt?: IWonewSer.PaginationPrams) {
    return defer(() => 
      axios.get('https://member.bilibili.com/x/web/archives', {
        params: {
          'status': 'is_pubing,pubed,not_pubed',
          'pn': opt?.page || '1',
          'ps': opt?.pageSize || '20',
          'coop': '1',
          'interactive': '1'
        },
        headers: {
          'accept': 'application/json, text/javascript, */*; q=0.01',
          'accept-language': 'zh-CN,zh;q=0.9',
          'cache-control': 'no-cache',
          'cookie': IHelper.covert2StandardCookieStr(cookies),
          'pragma': 'no-cache',
          'referer': 'https://www.bilibili.com/',
          'sec-ch-ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
          'sec-ch-ua-mobile': '?0',
          'sec-ch-ua-platform': '"macOS"',
          'sec-fetch-dest': 'empty',
          'sec-fetch-mode': 'cors',
          'sec-fetch-site': 'same-origin',
          'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
          'x-requested-with': 'XMLHttpRequest'
        }
      })
    ).pipe(
      map(d => {
        if (d.data.code !== 0) {
          throw new HttpException("Get post failed.", HttpStatus.BAD_REQUEST)
        }
        return {
          list: d.data.data.arc_audits.map((e: any) => this.formatPost(e)),
          total: d.data.data.page.count,
          page: d.data.data.page.pn,
          pageSize: d.data.data.page.ps,
        }
      })
    )
  }

  formatPost(post: any): IWonewSer.AccountPost {
    return {
      id: post.Archive.bvid,
      cover: post.Archive.cover,
      title: post.Archive.title,
      url: "",
      publish_date: moment(post.Archive.ptime * 1000).format("YYYY-MM-DD HH:mm:ss"),
      view_count: post.stat.view, // 播放
      like_count: post.stat.like, // 点赞
      coin_count: post.stat.coin, // 投币
      fav_count: post.stat.favorite, // 收藏
      share_count: post.stat.share, // 分享
      danmaku_count: post.stat.danmaku, // 弹幕
      reply_count: post.stat.reply, // 评论
    }
  }
}